#!/usr/bin/python3
# Copyright 2017 The ANGLE Project Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# gen_proc_table.py:
#  Code generation for entry point loading tables.
#  NOTE: don't run this script directly. Run scripts/run_code_generation.py.

import os
import sys
import registry_xml

out_file_name_gles = "../src/libGLESv2/egl_stubs_getprocaddress_autogen.cpp"
out_file_name_cl = "../src/libGLESv2/proc_table_cl_autogen.cpp"
out_file_name_cl_map = "../src/libOpenCL/libOpenCL_autogen.map"

strip_suffixes = ["ANGLE", "EXT", "KHR", "OES", "CHROMIUM", "OVR"]

template_cpp = """// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {data_source_name}.
//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// getProcAddress loader table:
//   Mapping from a string entry point name to function address.
//

{includes}
#define P(FUNC) reinterpret_cast<{cast}>(FUNC)

namespace {namespace}
{{

namespace
{{
struct ProcEntry
{{
    const char *name;
    __eglMustCastToProperFunctionPointerType func;
}};

bool CompareProc(const ProcEntry &a, const char *b)
{{
    return strcmp(a.name, b) < 0;
}}

// clang-format off
const ProcEntry g_procTable[] = {{
{proc_data}
}};
// clang-format on
}}  // anonymous namespace

__eglMustCastToProperFunctionPointerType GetProcAddress(Thread *thread, const char *procname)
{{
    if (procname == nullptr)
    {{
        return nullptr;
    }}

    const ProcEntry *entry =
        std::lower_bound(std::begin(g_procTable), std::end(g_procTable), procname, CompareProc);

    thread->setSuccess();

    if (entry == std::end(g_procTable) || strcmp(entry->name, procname) != 0)
    {{
        return nullptr;
    }}

    return entry->func;
}}

}}  // namespace {namespace}

"""

# FOR OPENCL
template_map_cpp = """// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {data_source_name}.
//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// proc_table:
//   Mapping from a string entry point name to function address.
//

{includes}
#define P(FUNC) reinterpret_cast<{cast}>(FUNC)

namespace {namespace}
{{

const ProcTable &GetProcTable()
{{
    static angle::base::NoDestructor<ProcTable> sProcTable(
        {{{proc_data}}});
    return *sProcTable;
}}

}}  // namespace {namespace}
"""

# FOR OPENCL
template_map = """/* GENERATED FILE - DO NOT EDIT.
 * Generated by {script_name} using data from {data_source_name}.
 *
 * Copyright 2021 The ANGLE Project Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * symbol version map: Maps versions to entry point names for a shared library.
 */
{symbol_maps}
"""

includes_gles = """#include "libANGLE/Thread.h"
#include "libGLESv2/entry_points_egl_autogen.h"
#include "libGLESv2/entry_points_egl_ext_autogen.h"
#include "libGLESv2/entry_points_gles_1_0_autogen.h"
#include "libGLESv2/entry_points_gles_2_0_autogen.h"
#include "libGLESv2/entry_points_gles_3_0_autogen.h"
#include "libGLESv2/entry_points_gles_3_1_autogen.h"
#include "libGLESv2/entry_points_gles_3_2_autogen.h"
#include "libGLESv2/entry_points_gles_ext_autogen.h"
#include "platform/PlatformMethods.h"

#include <iterator>
"""

includes_cl = """#include "libGLESv2/proc_table_cl.h"

#include "libGLESv2/entry_points_cl_autogen.h"

#include "anglebase/no_destructor.h"

// Using fully qualified entry point identifiers to make sure that missing entry points would not
// pick up the global declarations of OpenCL
"""

sys.path.append('../src/libANGLE/renderer')
import angle_format


def _get_annotations(versions):
    return ["%d_%d" % version for version in versions]


def main():

    # auto_script parameters.
    if len(sys.argv) > 1:
        inputs = [source for source in registry_xml.xml_inputs]
        outputs = [out_file_name_gles, out_file_name_cl, out_file_name_cl_map]
        if sys.argv[1] == 'inputs':
            print(','.join(inputs))
        elif sys.argv[1] == 'outputs':
            print(','.join(outputs))
        else:
            print('Invalid script parameters')
            return 1
        return 0

    glesxml = registry_xml.RegistryXML('gl.xml', 'gl_angle_ext.xml')

    for annotation in _get_annotations(registry_xml.GLES_VERSIONS):
        name_prefix = "GL_ES_VERSION_"
        if annotation[0] == '1':
            name_prefix = "GL_VERSION_ES_CM_"
        feature_name = "{}{}".format(name_prefix, annotation)
        glesxml.AddCommands(feature_name, annotation)

    glesxml.AddExtensionCommands(registry_xml.supported_extensions, ['gles2', 'gles1'])

    # Also don't add GLES extension commands to libGL proc table
    extension_commands = []
    for extension_name, ext_cmd_names in sorted(glesxml.ext_data.items()):
        extension_commands.extend(glesxml.ext_data[extension_name])
    for name in extension_commands:
        name_no_suffix = name
        for suffix in strip_suffixes:
            if name_no_suffix.endswith(suffix):
                name_no_suffix = name_no_suffix[0:-len(suffix)]

    gles_data = glesxml.all_cmd_names.get_all_commands()
    eglxml = registry_xml.RegistryXML('egl.xml', 'egl_angle_ext.xml')

    for annotation in _get_annotations(registry_xml.EGL_VERSIONS):
        name_prefix = "EGL_VERSION_"
        feature_name = "{}{}".format(name_prefix, annotation)
        eglxml.AddCommands(feature_name, annotation)

    eglxml.AddExtensionCommands(registry_xml.supported_egl_extensions, ['gles2', 'gles1'])

    gles_data.extend(eglxml.all_cmd_names.get_all_commands())

    gles_data.append("ANGLEGetDisplayPlatform")
    gles_data.append("ANGLEResetDisplayPlatform")
    gles_data = set(gles_data)

    all_functions = {}
    for function in gles_data:
        if function.startswith("gl"):
            all_functions[function] = "GL_" + function[2:]
        elif function.startswith("egl"):
            all_functions[function] = "EGL_" + function[3:]
        else:
            all_functions[function] = function

    proc_data = []
    for func, angle_func in sorted(all_functions.items()):
        proc_data.append('    {"%s", P(%s)},' % (func, angle_func))

    with open(out_file_name_gles, 'w') as out_file:
        output_cpp = template_cpp.format(
            script_name=os.path.basename(sys.argv[0]),
            data_source_name="gl.xml, gl_angle_ext.xml, egl.xml, egl_angle_ext.xml",
            includes=includes_gles,
            cast="__eglMustCastToProperFunctionPointerType",
            namespace="egl",
            proc_data="\n".join(proc_data),
            num_procs="std::size(g_procTable)")
        out_file.write(output_cpp)
        out_file.close()

    # libCL proc table
    clxml = registry_xml.RegistryXML('cl.xml')
    symbol_maps = []
    symbol_map_dependency = ""

    for major_version, minor_version in registry_xml.CL_VERSIONS:
        name_prefix = "CL_VERSION_"
        annotation = "%d_%d" % (major_version, minor_version)
        feature_name = "%s%s" % (name_prefix, annotation)
        clxml.AddCommands(feature_name, annotation)
        symbol_version = "OPENCL_%d.%d" % (major_version, minor_version)
        symbol_maps += ["\n%s {\n    global:" % symbol_version]
        symbol_maps += ['        %s;' % cmd for cmd in clxml.commands[annotation]]
        if not symbol_map_dependency:
            symbol_maps += ["    local:\n        *;\n};"]
        else:
            symbol_maps += ["} %s;" % symbol_map_dependency]
        symbol_map_dependency = symbol_version

    clxml.AddExtensionCommands(registry_xml.supported_cl_extensions, ['cl'])
    cl_commands = clxml.all_cmd_names.get_all_commands()
    proc_data = ['{"%s", P(::cl::%s)}' % (cmd, cmd) for cmd in cl_commands]

    with open(out_file_name_cl, 'w') as out_file:
        output_cpp = template_map_cpp.format(
            script_name=os.path.basename(sys.argv[0]),
            data_source_name="cl.xml",
            includes=includes_cl,
            cast="void *",
            namespace="cl",
            proc_data=",\n         ".join(proc_data))
        out_file.write(output_cpp)
        out_file.close()

    with open(out_file_name_cl_map, 'w') as out_file:
        output_map = template_map.format(
            script_name=os.path.basename(sys.argv[0]),
            data_source_name="cl.xml",
            symbol_maps="\n".join(symbol_maps))
        out_file.write(output_map)
        out_file.close()

    return 0


if __name__ == '__main__':
    sys.exit(main())
